home *** CD-ROM | disk | FTP | other *** search
- /*______________________________________________________*/
- /* Sound I/O Demo */
- /* by */
- /* RICHARD P. COLLYER */
- /* Developer Technical Support */
- /* Apple Computer, Inc. */
- /* 11/14/91 */
- /*______________________________________________________*/
-
- /*
-
- Notes:
- • There is a hardware limitation which keeps this code from
- working on Macintosh computers which do not support stereo sound,
- the Mac LC is an example of a machine which cn not run this code.
- It isn't possible to record and playback at the same time unless
- the machine can support stereo.
-
- • Be careful writing interrupt routines in C. If you use a global
- array, the MPW C compilier may optimize the array's address into
- a register before you can restore the applition's A5. This will
- incorrectly calculate the address of the array. Using a "wrapper"
- for setting A5 before calling the interrupt routine will prevent
- this problem.
-
- • When using the continuous recording feature of the Apple built-in
- sound input device, there is a bug to be aware of. If the recording
- buffer is not a multiple of the device's internal buffer then you
- will collect garbage and not audio data. This means we need to be
- careful to create a buffer of the right size. This is also good
- because the input device will be able to more effeciently record
- data into our buffer. So this is a doubly good idea.
-
- */
-
- /**********************************/
- /* #includes */
- /**********************************/
-
- #include <2BufRecordToBufCmd.h>
-
- /**********************************/
- /* prototypes for the functions */
- /**********************************/
-
- void TestTheSystem (void);
- void GetSoundDeviceInfo (void);
- void SetUpSounds (Handle *bufferHandle, short *headerSize);
- int FindHeaderSize (void);
- void BuildRecordStruct (Handle bufferHandle);
- void PlayBuffer (Handle bufferHandle);
- void ResetSoundHeader (Handle bufferHandle);
- void ExitWithMessage (char *message, OSErr error);
- void TimeToQuit (void);
- pascal void MyRecComp (SPBPtr inParamPtr);
- void RealCompletion (void);
-
- /**********************************/
- /* Application Globals */
- /**********************************/
-
- SPBPtr gRecordStruct;
- SndChannelPtr gChannel;
- Handle gBufferHandle[kNumberOfBuffers];
- Fixed gSampleRate;
- long gInternalBuffer;
- long gSoundRefNum = 0;
- unsigned long gSampleAreaSize; // size of the sample area in the snd handle
- int gHeaderSize; // Size of the Header to be skipped in for the bufferCmd
- short gDataStart; // Size of the Entire 'snd ' header
- short gSampleSize;
- short gNumberOfChannels;
- short gWhichRecordBuffer = 0;
- OSErr gError;
- OSType gCompression;
-
- /**********************************/
- /* TestTheSystem */
- /**********************************/
-
- void TestTheSystem (void)
-
- /* use Gestalt to make sure the app will work on the system*/
-
- {
- long feature;
-
- gError = Gestalt(gestaltSoundAttr, &feature);
- if (!gError) {
- /* First Check to see that Sound Input is available */
- if ( !(feature & (1 << gestaltHasSoundInputDevice)) )
- ExitWithMessage ("No Sound Input Device, so The app can't run", 0);
-
- /* Second Check to see that the hardware supports stereo, if not then I can't
- record and play sounds at the same time */
- if ( !(feature & (1 << gestaltStereoCapability)) )
- ExitWithMessage ("No Stereo support, so The app can't run", 0);
- }
- else
- ExitWithMessage ("Gestalt failed in TestTheSystem", gError);
-
- return;
- }
-
- /**********************************/
- /* GetSoundDeviceInfo */
- /**********************************/
-
- void GetSoundDeviceInfo (void)
-
- /* Extract the information about the Sound Input Device to build the Sound Header */
-
- {
- long value;
-
- /* Get the sample rate information for the snd header */
-
- gError = SPBGetDeviceInfo (gSoundRefNum,siSampleRate, (Ptr) &gSampleRate);
- if (gError != noErr)
- ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siSampleRate", gError);
-
- /* Get the sample size information for the snd header */
-
- gError = SPBGetDeviceInfo (gSoundRefNum,siSampleSize, (Ptr) &gSampleSize);
- if (gError != noErr)
- ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siSampleSize", gError);
-
- /* Get the compression type information for the snd header */
-
- gError = SPBGetDeviceInfo (gSoundRefNum,siCompressionType, (Ptr) &gCompression);
- if (gError != noErr)
- ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siCompressionType", gError);
-
- /* Get the number of input channels for the snd header */
-
- gError = SPBGetDeviceInfo (gSoundRefNum,siNumberChannels, (Ptr) &gNumberOfChannels);
- if (gError != noErr)
- ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siNumberChannels", gError);
-
- /* Get the size of the internal sound buffer for the snd buffer */
-
- gError = SPBGetDeviceInfo (gSoundRefNum,siDeviceBufferInfo, (Ptr) &gInternalBuffer);
- if (gError != noErr)
- ExitWithMessage ("SPBGetDeviceInfo failed in GetSoundDeviceInfo while looking at siDeviceBufferInfo", gError);
-
- value = kMilliSecondsOfSound;
- gError = SPBMillisecondsToBytes(gSoundRefNum, &value);
- if (gError != noErr)
- ExitWithMessage ("\pSPBMillisecondsToBytes failed in GetSoundDeviceInfo", gError);
-
- /* Round the buffer size to a multiple of the internal buffer size */
- gSampleAreaSize = (value / gInternalBuffer) * gInternalBuffer;
-
- return;
- }
-
- /**********************************/
- /* SetUpSounds */
- /**********************************/
-
- void SetUpSounds (Handle *bufferHandle, short *headerSize)
-
- /* SetUpSounds is a routine which allocates a snd buffer with the proper header
- and sample size, passing the handle and the real header size back to the caller. */
-
- {
- GetSoundDeviceInfo();
-
- /* Allocate largest Handle we could need */
- *bufferHandle = NewHandle(gSampleAreaSize + kEstimatedHeaderSize);
- gError = MemError();
- if (gError != noErr || *bufferHandle == nil)
- ExitWithMessage ("NewHandle failed in SetUpSounds", gError);
-
- /* Set up the header. After this call, we'll know how big the header size is */
- gError = SetupSndHeader (*bufferHandle, gNumberOfChannels, gSampleRate, gSampleSize, gCompression,
- kMiddleC, 0, headerSize);
- if (gError != noErr)
- ExitWithMessage ("SetupSndHeader failed in SetUpSounds", gError);
-
- /* Size the handle down to the size we really need */
- SetHandleSize(*bufferHandle, (Size) *headerSize + gSampleAreaSize);
- gError = MemError();
- if (gError != noErr)
- ExitWithMessage ("SetHandleSize failed in SetUpSounds", gError);
-
- /* Move the handle high and lock it */
- MoveHHi (*bufferHandle);
- gError = MemError();
- if (gError != noErr)
- ExitWithMessage ("MoveHHi failed in SetUpSounds", gError);
-
- HLock (*bufferHandle);
- gError = MemError();
- if (gError != noErr)
- ExitWithMessage ("HLock failed in SetUpSounds", gError);
-
- return;
- }
-
- /**********************************/
- /* FindHeaderSize */
- /**********************************/
-
- int FindHeaderSize (void)
-
- /* This routine returns the number of bytes of the buffer we need to skip
- when calling bufferCmd. The first several bytes of the sound header need to be skipped
- so that the bufferCmd will be pointing at a SoundHeader Record and not an 'snd ' resource
- header. The equations which are used in this routine are from Inside Macintosh VI page 20-22 */
-
- {
- int headerSize;
- short highByte, lowByte;
- short format, numberOfSynths, numberOfCommands;
-
- highByte = **gBufferHandle[0];
- lowByte = *(*gBufferHandle[0] + 1);
- format = (highByte << 8) + lowByte;
-
- switch (format) {
- case 1: /* Format 1 snd */
- headerSize = kBaseHeaderSize;
-
- // find the number of Synths in the snd header
- highByte = *(*gBufferHandle[0] + 2);
- lowByte = *(*gBufferHandle[0] + 3);
- numberOfSynths = (highByte << 8) + lowByte;
- headerSize += numberOfSynths * kSynthSize;
-
- // find the number of commands in the 'snd ' header
- highByte = *(*gBufferHandle[0] + headerSize - 2);
- lowByte = *(*gBufferHandle[0] + headerSize - 1);
- numberOfCommands = (highByte << 8) + lowByte;
- headerSize += numberOfCommands * kCmdSize;
- break;
-
- case 2: /* Format 2 snd */
- headerSize = kBaseHeaderSize;
-
- // find the number of commands in the 'snd ' header
- highByte = *(*gBufferHandle[0] + 4);
- lowByte = *(*gBufferHandle[0] + 5);
- numberOfCommands = (highByte << 8) + lowByte;
- headerSize += numberOfCommands * kCmdSize;
- break;
-
- default:
- break;
- }
-
- return (headerSize);
- }
-
- /**********************************/
- /* BuildRecordStruct */
- /**********************************/
-
- void BuildRecordStruct (Handle bufferHandle)
-
- /* build the gRecordStruct pointer and fill in the fields */
-
- {
- gRecordStruct = (SPBPtr) NewPtr(sizeof (SPB));
- if (gRecordStruct == nil)
- ExitWithMessage ("NewPtr failed in BuildRecordStruct", gError);
-
- gRecordStruct->inRefNum = gSoundRefNum;
- gRecordStruct->count = gSampleAreaSize;
- gRecordStruct->milliseconds = 0;
- gRecordStruct->bufferLength = gSampleAreaSize;
- gRecordStruct->bufferPtr = (Ptr) ((*bufferHandle) + gDataStart);
- gRecordStruct->completionRoutine = (ProcPtr) MyRecComp;
- gRecordStruct->interruptRoutine = nil;
- gRecordStruct->userLong = SetCurrentA5();
- gRecordStruct->error = 0;
- gRecordStruct->unused1 = 0;
-
- return;
- }
-
- /**********************************/
- /* PlayBuffer */
- /**********************************/
-
- void PlayBuffer (Handle bufferHandle)
-
- /*This routine takes an 'snd ' buffer and a sound channel and turns the information into a
- bufferCmd to the channel. */
-
- {
- SndCommand localSndCmd;
-
- localSndCmd.cmd = bufferCmd;
- localSndCmd.param1 = 0;
- localSndCmd.param2 = (long) ((*bufferHandle) + gHeaderSize);
-
- gError = SndDoCommand (gChannel, &localSndCmd, false);
- if (gError != noErr)
- DebugStr("\pSndDoCommand failed in PlayBuffer (type 'g' return)");
-
- return;
- }
-
- /**********************************/
- /* ExitWithMessage */
- /**********************************/
-
- void ExitWithMessage (char *message, OSErr error)
-
- /* All errors are passed to this routine to display a message and the error result.
- it also exits the application, because this sample demos the sound manager, not
- error handleing. */
-
- {
- GrafPtr savePort;
- DialogPtr myDialog;
- short itemtype, itemHit;
- Handle itemHand;
- Rect itemRect;
- char *errStrPtr, errStr[256];
-
- errStrPtr = &errStr;
-
- GetPort(&savePort);
- myDialog = GetNewDialog(kErrorDialogID, nil, (WindowPtr) -1);
- SetPort(myDialog);
-
- numtostring(error,errStrPtr);
- GetDItem(myDialog,kErrNumStatText,&itemtype,&itemHand,&itemRect);
- setitext(itemHand, errStrPtr);
-
- GetDItem(myDialog,kMsgStatText,&itemtype,&itemHand,&itemRect);
- setitext(itemHand, message);
-
- do {
- ModalDialog(nil,&itemHit);
- } while (itemHit != kOKButton);
-
- DisposDialog(myDialog);
- SetPort(savePort);
-
- TimeToQuit ();
- }
-
-
- /**********************************/
- /* TimeToQuit */
- /**********************************/
-
- void TimeToQuit (void)
-
- /* Once I am out of the loop it is time to clean up - stop the currently playing sound,
- Dispose of the Channel, close the input driver, and dispose of the Buffer handles and
- gRecordStruct Ptr. */
-
- {
- short index, recordingStat, meterlevel;
- unsigned long totalSamples, numberOfSamples, totalMSec, numberOfMSec;
-
- // check each global to make sure they were allocated before disposing of them
-
- if (gSoundRefNum != 0) {
- gError = SPBGetRecordingStatus (gSoundRefNum, &recordingStat, &meterlevel,
- &totalSamples, &numberOfSamples, &totalMSec, &numberOfMSec);
- if (gError != noErr)
- DebugStr("\SPBGetRecordingStatus failed in TimeToQuit (type 'g' return)");
-
- if (recordingStat > 0) {
- // make sure that recording has stopped before I close the sound driver
- gError = SPBStopRecording (gSoundRefNum);
- if (gError != noErr)
- DebugStr("\pSPBStopRecording failed in TimeToQuit (type 'g' return)");
- }
-
- gError = SPBCloseDevice (gSoundRefNum);
- if (gError != noErr)
- DebugStr("\pSPBCloseDevice failed in TimeToQuit (type 'g' return)");
- }
-
- if (gBufferHandle[0] != nil)
- for (index = 0; index < kNumberOfBuffers; ++index)
- DisposeHandle (gBufferHandle[index]);
-
- if (gRecordStruct != nil)
- DisposePtr ((Ptr) gRecordStruct);
-
- if (gChannel != nil) {
- gError = SndDisposeChannel (gChannel,true);
- if (gError != noErr)
- DebugStr("\pSndDisposeChannel failed in TimeToQuit (type 'g' return)");
- }
-
- ExitToShell();
- }
-
- /**********************************/
- /* main */
- /**********************************/
-
- main()
- {
- short index, contOnOff = 1;
-
- InitGraf(&qd.thePort);
- FlushEvents(everyEvent, 0);
- InitWindows();
- InitDialogs(nil);
-
- TestTheSystem (); // test the system to make sure the app will work
-
- /* Open sound input drive (whichever one is selected in the sound cdev) */
-
- gError = SPBOpenDevice (kDefaultDriver, siWritePermission, &gSoundRefNum);
- if (gError != noErr)
- ExitWithMessage ("SPBOpenDevice failed in main", gError);
-
- /* turn on continuous recording */
-
- gError = SPBSetDeviceInfo (gSoundRefNum,siContinuous, (Ptr) &contOnOff);
- if (gError != noErr)
- ExitWithMessage ("SPBSetDeviceInfo failed in main", gError);
-
- /* build the kNumberOfBuffers snd Buffers */
-
- for (index = 0; index < kNumberOfBuffers; ++index)
- SetUpSounds (&gBufferHandle[index], &gDataStart);
-
- /* determine the part of the header which needs to be skipped before calling bufferCmd */
-
- gHeaderSize = FindHeaderSize();
-
- /* build the gRecordStruct pointer and fill in the fields */
-
- BuildRecordStruct (gBufferHandle[gWhichRecordBuffer]);
-
- /* open the sound channel which I will need to play from */
-
- gChannel = nil;
- // initNoInterp gets rid of the clicks between the buffers
- gError = SndNewChannel (&gChannel, sampledSynth, initNoInterp, nil);
- if (gError != noErr)
- ExitWithMessage ("SndNewChannel failed in main", gError);
-
- gError = SPBRecord (gRecordStruct, true); // start recording
- if (gError != noErr)
- ExitWithMessage ("SPBRecord failed in main", gError);
-
- while (!Button() || (gRecordStruct->error < noErr)); /* main loop of the app */
-
- // quitting time
-
- if (gRecordStruct->error < noErr)
- ExitWithMessage ("\pAn error occurred while recording", gRecordStruct->error);
- else
- TimeToQuit ();
- }
-
- /**********************************/
- /* MyRecComp */
- /**********************************/
-
- pascal void MyRecComp (SPBPtr inParamPtr)
-
- // This is the Completion Routine which is called every time the recording Buffer,
- // is full. This routine needs to setup A5 to be the application's A5 which was being
- // saved in the userLong of the parameter block.
-
- // Due to the MPW C compiler optimization scheme, access to global arrays will be pointed
- // to in an address register as an offset of A5. This will happen before we have a chance
- // to set A5 to our application's A5. To avoid this we need to first restore our A5 and
- // then call the completion routine.
-
- {
- long storeA5;
-
- /* Set A5 so the completion routine has access to the Application Globals */
-
- storeA5 = SetA5 (inParamPtr->userLong);
-
- RealCompletion ();
-
- storeA5 = SetA5 (storeA5);
-
- return;
- }
-
- /**********************************/
- /* RealCompletion */
- /**********************************/
-
- void RealCompletion (void)
-
- // Setup the current snd handle to have the correct size of the sample data we've just
- // finished recording. The size of the sample data is in the count field of the
- // recording parameter block. Then play this new buffer of data. Finally, we will
- // start another recording after switching to the other buffer.
-
- {
- OSErr err;
- SoundHeaderPtr header;
-
- // if there has been an error then do nothing
- if (gRecordStruct->error < 0) return;
-
- // get pointer to SoundHeader and update length value in the header
- header = (SoundHeaderPtr) (*(gBufferHandle[gWhichRecordBuffer]) + gHeaderSize);
- header->length = gRecordStruct->count;
-
- PlayBuffer (gBufferHandle[gWhichRecordBuffer]); // Play the buffer
-
- gWhichRecordBuffer = NextBuffer (gWhichRecordBuffer); // move on to next buffer
-
- // update gRecordStruct
- gRecordStruct->bufferPtr = (*(gBufferHandle[gWhichRecordBuffer]) + gDataStart);
- gRecordStruct->milliseconds = 0;
- gRecordStruct->count = gSampleAreaSize;
- gRecordStruct->bufferLength = gSampleAreaSize;
-
- err = SPBRecord (gRecordStruct, true); // queue up another record
- if (err)
- DebugStr ("\pSPBRecord died in RealCompletion");
- }
-